我覺得 Ansible 學習上比較困難的地方在於它主要是學習設定檔的配置,若要實作,很難設計一個有意義的場景去試驗那些配置,若想要嘗試的模組種類過多,場景就會變得很複雜,而且修改配置參數後,想要馬上得到結果似乎也有點困難。這也跟經驗有關,如果是一個對於主機管理配置經驗豐富的維運人員,可能看到這些功能模組,立刻就可以想到它適用的地方,像我這種沒什麼經驗的人,經常會發生這個功能有什麼用、這個功能在做什麼、這個功能什麼情況下會被用到,諸如此類的疑惑。總之我想說的是,因為接下來的部分很難設計一個實作場景,所以我的作法會是以文件內容配合應試能力目標來作介紹,感覺會比較無聊一點。
除了 playbook 及 inventory 這兩個供 Ansible 專案使用的設定檔,Ansible 本身也有一個設定檔,路徑為 /etc/ansible/ansible.cfg
。裡面有一些值可以以專案中的 ansible.cfg 覆寫,例如 inventory 設定檔的路徑。昨天在下 ansible-playbook 指令時用 -i 指定了 inventory 路徑,這個值可以直接指定在 ansible.cfg 中。請在昨天的專案目錄中新增一個 ansible.cfg 檔案,並加入以下內容
[defaults]
inventory=hosts
這樣就可以直接執行 ansible-playbook web.yml,不需要指定 inventory 路徑。關於 ansible.cfg 有那些值可以設定,可參考 /etc/ansible/ansible.cfg 註解的內容。
先來看 inventory 設定檔的介紹。inventory 設定主要作兩件事,第一件事是把管理節點分類,在 playbook 就可以用這些「類」作為操作的對象。第二件事是設置各管理節點相關參數,例如 SSH port 號、登入名稱等等,這類型的參數稱為 behaviroal inventory parameter。
inventory 可以使用幾種不同的格式,一種是我們前面用的類似 INI 的格式,另一種是 YAML。以文件上的說法預設似乎是 INI,所以我們就用 INI 的格式來作介紹。
單台機器的設定,可以用以下這種格式,最前面是主機名稱,然後跟著兩個參數,port 和 IP。如果沒有主機名稱,找個別名來用也可以,但後面就要用 ansible_host 來指定主機 IP。還有那些 behavioral inventory parameter,可以參考 https://docs.ansible.com/ansible/2.6/user_guide/intro_inventory.html#list-of-behavioral-inventory-parameters。可以在單台主機中指定變數 (variable) 並賦值,一樣是用 key=value
的型式指定,變數可用於 playbook 中。
ansible.test.com ansible_port=5555 ansible_host=192.0.2.50 maxRequestsPerChild=808
接下來是分群 (group),類別名稱放在中括號 [ ]
裡面,下面再列出要放在該類別下的機器。類別可以有層級,以 [<group>:children]
的方式指定,一台機器也可以放在多個類別中。group 的變數則放在 [<group>:vars]
之下,範例如下:
[atlanta]
host1
host2
[raleigh]
host2
host3
[southeast:children]
atlanta
raleigh
[southeast:vars]
some_server=foo.southeast.example.com
halon_system_timeout=30
self_destruct_countdown=60
escape_pods=2
[usa:children]
southeast
northeast
usa 這個 group 有兩個子 group,分別是 southeast、northeast,southeast 也有兩個子群組 atlanta 和 raleigh,分別指定了兩台主機。而 southeast 這個 group 指定了四個變數。
接下來我們來看文件中對 playbook 的介紹,以下是從文件中取出的 playbook 範例:
---
- hosts: webservers
remote_user: jack
become: yes
tasks:
- name: ensure apache is at the latest version
yum:
name: httpd
state: latest
- name: write the apache config file
template:
src: /srv/httpd.j2
dest: /etc/httpd.conf
- hosts: databases
remote_user: root
tasks:
- name: ensure postgresql is at the latest version
yum:
name: postgresql
state: latest
- name: ensure that postgresql is started
service:
name: postgresql
state: started
這個 playbook 中有兩個 play,每個 play 的開頭部分有 hosts、remote_user、become 三個 directive,昨天已經介紹過 hosts 是指這個 play 要作用的主機,remote_user 則是以那個身份名稱登入遠端主機,become 的值用 yes 和 True 是相同的,在這裡表示要用特權身分執行之後的工作,這部分之後會再說明。
接下來看 tasks 的部分,每個 task 會先有 name,描述這個 task 的內容,它是註解的作用,也會成為 Ansible 輸出的一部分。name 也可以用於說明此 play 的整體作用,請參考昨天的範例。在 task 中可以指定不同的動作,依指定的動作呼叫不同的模組來執行這些動作,例如 yum、service 等等,格式是 module: option。這裡參考【應試目標能力】對重要的 task 模組略為說明,更多的模組可參考 https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html#all-modules。
用來修改設定檔案的屬性,包括 symbolic link,範例如下:
- file:
path: /etc/foo.conf
owner: foo
group: foo
mode: 0644
- file:
src: /file/to/link/to
dest: /path/to/symlink
owner: foo
group: foo
state: link
第二個範例昨天有看過相同的用法:
- name: enable configuration
file: >
dest=/etc/nginx/sites-enabled/default
src=/etc/nginx/sites-available/default
state=link
差別在於文件中的範例,file 的參數是 key: value
的型式,而昨天的範例是 key=value
。key: value
是 YAML 的格式,表示這裡的參數有點像是用 YAML 的鍵值方式送過去的,而 key=value 這種型式是以字串傳入參數,file 後面的 ">" 符號,就書裡面的說明是將下面的換行符號以空格取代,所以下面的三行整個是一個字串,這個字串再送給 file 作為參數。因為文件是用 YMAL 的 key: value 作為參數型式,那我們也就使用這種型式吧。
用來將本機(控制台)或遠端(管理節點)的檔案複製到遠端的指定位置,範例如下:
- name: example copying file with owner and permissions
copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
group: foo
mode: 0644
backup: yes
- name: Copy a "sudoers" file on the remote machine for editing
copy:
src: /etc/sudoers
dest: /etc/sudoers.edit
remote_src: yes
validate: /usr/sbin/visudo -cf %s
第二個範例的 remote_src: yes 表示這個檔案的來源是遠端機器,而不是本機。validate 是指在複製前會先用這個指令驗證,%s 會代入檔案名稱,但文件中沒有特別說怎麼判定驗證有無通過,若沒通過會發生什麼事。backup 會在目標檔案已存在時,先將檔案存一複本。
用來將本機的樣版處理過後複製到遠端的指定位置。所謂的樣版是指內容含有變數的檔案,而變數的內容則根據情況產生,例如使用者名稱、時間等等。Ansible 的樣版使用 Jinja2 引擎,Jinja2 相關部分之後會再說明。基本上 template 的參數和上面 copy 幾乎相同,但沒有 remote_src,因為樣版一定要在本地端處理過,裡頭的變數才會有值。昨天的範例中也有用到 Jinja2,這裡就不再提供其他範例。
用來修改 INI 型式的檔案內容,可以單獨處理特定段落 (section) 的特定鍵值,範例如下:
- name: Ensure "temperature=cold is in section "[drinks]" in specified file
ini_file:
path: /etc/anotherconf
section: drinks
option: temperature
value: cold
backup: yes
會保證 /etc/anotherconf
出現如下設置。
[drinks]
temperature=cold
有一個 state 參數,若設成 absent,表示這個 option 或 section 要被刪除。
利用正規表示式來修改指定檔案的內容,範例如下:
- lineinfile:
path: /etc/selinux/config
regexp: '^SELINUX='
line: 'SELINUX=enforcing'
- lineinfile:
path: /etc/sudoers
state: absent
regexp: '^%wheel'
上面的範例將 /etc/selinux/config
檔案位於行開頭的 SELINUX=
改成 SELINUX=enforcing
,下面的範例將 /etc/sudoers
位於行開頭的 %wheel
刪掉。
使用 GNU patch 工具來實施 patch,在遠端機器必須先安裝 GNU patch 工具。patch 範例如下:
- name: Apply patch to one file
patch:
src: /tmp/index.html.patch
dest: /var/www/index.html
利用正規表示式取代指定檔案中的字串,範例如下:
- replace:
path: /etc/hosts
regexp: '(\s+)old\.host\.name(\s+.*)?$'
replace: '\1new.host.name\2'
backup: yes
管理使用者帳號,包括新增、修改、刪除,也可以協助產生 SSH 金鑰,範例如下:
- name: Add the user 'johnd' with a specific uid and a primary group of 'admin'
user:
name: johnd
comment: John Doe
uid: 1040
group: admin
generate_ssh_key: yes
ssh_key_bits: 2048
ssh_key_file: .ssh/id_rsa
新增或刪除群組,範例如下:
- name: Ensure group "somegroup" exists
group:
name: somegroup
state: present
在遠端機器上執行指令,範例如下:
- name: return motd to registered var
command: cat /etc/motd
register: mymotd
- name: Run the command if the specified file does not exist.
command: /usr/bin/make_database.sh arg1 arg2
args:
creates: /path/to/database
# You can also use the 'args' form to provide the options.
- name: This command will change the working directory to somedir/ and will only run when /path/to/database doesn't exist.
command: /usr/bin/make_database.sh arg1 arg2
args:
chdir: somedir/
creates: /path/to/database
第一個範例中的 register: mymotd
是指將指令 cat /etc/motd
的結果儲存到 mymotd
這個變數中,變數的指定方式會再說明。從範例中看來有些參數要放在 args
下,例如 creates
參數接的是檔案名稱,表示這個檔案若存在,這一整個指令就不會執行。與其作用相反的是 removes
指令,若後面的參數檔案存在,這個指令才會被執行。
和 command
一樣是用於在遠端機器上執行指令,但 shell
適用於指令中會用到 shell 功能,例如重導、管線這類的指令,請看範例:
- name: Change the working directory to somedir/ before executing the command.
shell: somescript.sh >> somelog.txt
args:
chdir: somedir/
用來管理系統服務,像是 systemd、upstart 等,範例如下:
- name: Start service httpd, if not started
service:
name: httpd
state: started
- name: Restart service httpd, in all cases
service:
name: httpd
state: restarted
- name: Enable service httpd, and not touch the state
service:
name: httpd
enabled: yes
name 是服務名稱,state 有四個值,started、stopped、 restarted、reloaded,其中 restarted 和 reloaded 一定會執行,而 started 和 stopped 則是看目前服務是否啟動而定。enabled 表示是否開機時就要帶起此服務。文件中有一句話 At least one of state and enabled are required,而且標粗,我想應該是說 state 的四個狀態一定要指定其中一種,若沒有用到 state 這個參數,那一定要有 enabled 參數。為什麼不是寫 At least one of state "or" enabled are required?文件的寫法我本來認為是 state 和 enabled 都一定要有,但是看它的範例,並沒有兩個同時設定,所以就把它解讀成 or。喔,或者它應該是 one of "state and enabled",state 和 enabled 至少要出現一個的意思。
管理 systemd 系統服務,範例如下。其實和 service 差不多,不過有些參數行為像是 daemon-reload 可能是 systemd 才有的。
- name: enable service httpd and ensure it is not masked
systemd:
name: httpd
enabled: yes
masked: no
- name: enable a timer for dnf-automatic
systemd:
name: dnf-automatic.timer
state: started
enabled: yes
- name: just force systemd to reread configs (2.4 and above)
systemd:
daemon_reload: yes
管理 cron.d 和 crontab 排程工作內容,以及環境變數,可新增或刪除,範例如下:
- name: Ensure a job that runs at 2 and 5 exists. Creates an entry like "0 5,2 * * ls -alh > /dev/null"
cron:
name: "check dirs"
minute: "0"
hour: "5,2"
job: "ls -alh > /dev/null"
- name: 'Ensure an old job is no longer present. Removes any job that is prefixed by "#Ansible: an old job" from the crontab'
cron:
name: "an old job"
state: absent
- name: Creates an entry like "APP_HOME=/srv/app" and insert it after PATH declaration
cron:
name: APP_HOME
env: yes
value: /srv/app
insertafter: PATH
這個模組除了 cron job 外還可以用來處理環境變數,有點意外。在新增排程工作時,寫到檔案裡的項目會加上 #Ansible: <name>
這樣的註解,name 的部分可由參數設定。
以 apt 或 yum 來管理套件,主要應該是安裝或移除吧。範例如下:
- name: Update repositories cache and install "foo" package
apt:
name: foo
update_cache: yes
- name: Install apache httpd but avoid starting it immediately (state=present is optional)
apt:
name: apache2
state: present
environment:
RUNLEVEL: 1
- name: install the latest version of Apache
yum:
name: httpd
state: latest
- name: remove the Apache package
yum:
name: httpd
state: absent
設定 .deb 套件,範例如下:
- name: set to generate locales
debconf:
name: locales
question: locales/locales_to_be_generated
value: en_US.UTF-8 UTF-8, fr_FR.UTF-8 UTF-8
vtype: multiselect
- name: Accept oracle license
debconf:
name: oracle-java7-installer
question: shared/accepted-oracle-license-v1-1
value: 'true'
vtype: select
這個我不太懂,所以請自行參考範例。
從 Git 儲存庫取出 (checkout) 需要的檔案,範例如下:
# Example git checkout from Ansible Playbooks
- git:
repo: 'https://foosball.example.org/path/to/repo.git'
dest: /srv/checkout
version: release-0.22
version 可以是 HEAD 這類的引用,或者是提交的 hash 字串、分支名稱、標籤名稱等。
在執行時印出述句訊息以作為 debug 用,像是變數內容(用兩個大括號 {{ }} 包起來的部分),範例如下:
# Example that prints the loopback address and gateway for each host
- debug:
msg: "System {{ inventory_hostname }} has uuid {{ ansible_product_uuid }}"
今天介紹了 inventory 及 playbook 兩種設定檔的主要結構,以及在應試目標能力中提及比較重要的 playbook task。明天繼續介紹 handler 等其他在 playbook 中會用到的設置。